package cz.spock.core.service;

import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.logging.Logger;

import org.apache.commons.validator.EmailValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.google.appengine.api.datastore.Email;
import com.google.appengine.repackaged.org.joda.time.DateTime;
import com.googlecode.objectify.Key;

import cz.spock.core.entity.registration.Registration;
import cz.spock.core.entity.security.AccountSecurity;
import cz.spock.core.entity.Account;
import cz.spock.exception.DataException;
import cz.spock.exception.InvalidDataException;
import cz.spock.exception.InvalidInputException;

/**
 * status: implemented, not tested
 * 
 * @author miso
 *
 */
@Component
public class RegistrationService extends BaseService {
    
    @Autowired
    private UtilService utilService;
    
//    public void setUtilService(UtilService utilService) {
//        this.utilService = utilService;
//    }
    
    private static final Logger log = Logger.getLogger(RegistrationService.class.getName());

    
    /**
     * receives registration request data
     * - email and valid must be set, others can be null - default values will be used
     * - validHours specifies home many hours will be link valid, if value set to 0, valid time will be set to one day
     * 
     * validates email
     * creates and persists registration and sends email
     * returns registration object with assigned id
     * 
     * @param email
     * @return
     */
    public Registration createRegistration(String email, String invitedBy, String accType, boolean valid, int validHours) {
        // validate data
        if(email == null) {
            log.fine("email is null");            
            throw new InvalidInputException("Email is required.", 412);
        }
        if (!EmailValidator.getInstance().isValid(email)) {
            log.fine("email (" +email+ ") is not valid");
            throw new InvalidInputException("Email is not valid.", 4121);
        }
        if (!utilService.isEmailUnique(new Email(email))) {
            log.fine("email (" +email+ ") is already used");
            throw new InvalidDataException("Email ("+email+") is already used.", 421);
        }
        // default account type
        if(accType == null) accType = "user";
        // set inviter
        Key<Account> inv = null;
        if(invitedBy != null) inv = dao.fact().stringToKey(invitedBy);
        // create registration
        Registration newReg = new Registration();
        newReg.setValidLink(true);
        newReg.setInvitedBy(inv);
        newReg.setAccountType("user");
        newReg.setCreated(new Date());
        newReg.setEmail(new Email(email));
        
        newReg.setLink(utilService.shaHash(email + newReg.getCreated().hashCode(), null));

        System.out.println("hash=" + newReg.getLink());        
        
        sendRegistrationEmail(newReg.getEmail().getEmail(), newReg.getLink());
        
        // validUntil default
        if(validHours == 0) {
            newReg.setValidUntil(new DateTime(newReg.getCreated()).plusDays(1).toDate());
        } else {
            newReg.setValidUntil(new DateTime(newReg.getCreated()).plusHours(validHours).toDate());
        }
        // persist registration with control
        return this.save(newReg);
    }
    
    
    /**
     * Returns null if registration request was accepted, error message
     * otherwise.
     * 
     * @param emailStr
     * @return
     * @throws UnsupportedEncodingException
     * @throws NoSuchAlgorithmException
     */
    @Deprecated //use createRegistration() instead
    public void serveRegistrationRequest(String emailStr)
            throws NoSuchAlgorithmException, UnsupportedEncodingException {
        Email email = new Email(emailStr);
        if(dao.ofy().query(Registration.class).filter("email", email).get() != null) {
            throw new DataException("DataException: Email "+email.getEmail()+
                    " is already used (in table of requests).");
        }
        if (dao.ofy().query(Account.class).filter("email", email).get() != null) {
            throw new DataException("DataException: Email "+email.getEmail()+
                    " is already used (in table of accounts).");
        }
        Registration newReg = new Registration();
        newReg.setValidLink(true);
        newReg.setInvitedBy(null);
        newReg.setAccountType("user");
        newReg.setCreated(new Date());
        newReg.setEmail(email);
        String beforeHash = email.getEmail() + newReg.getCreated().hashCode();
        newReg.setLink(utilService.shaHash(beforeHash, null));
        log.config("hash: " + beforeHash + " = " + newReg.getLink());
        System.out.println("hash=" + newReg.getLink());        
        sendRegistrationEmail(newReg.getEmail().getEmail(), newReg.getLink());
        dao.ofy().put(newReg);
    }
    
    /**
     * verifies registration link in database
     * 
     * @param link
     * @return true if link is valid, false otherwise
     */
    public boolean verifyRegistrationLink(String link) {
        Registration reg = dao.ofy().query(Registration.class).filter("link", link).get();    
        if (reg == null)
            return false;
        if(reg.getValidUntil() != null) {
            if(reg.getValidUntil().before(new Date())) {
                return false;
            }
        }
        return reg.isValidLink();
    }
    
    /**
     * returns email address associated to given hash link
     * email is returned also in case when link is not valid but is stil present in database!
     * 
     * @param link
     * @return email or null if link is not found
     */
    public String getEmailForLink(String link) {
        Registration reg = dao.ofy().query(Registration.class).filter("link", link).get();    
        if (reg == null)
            return null;        
        return reg.getEmail().getEmail();
    }
    
    
    /**
     * sends email with one-time link
     */
    public void sendRegistrationEmail(String email, String link) {
        utilService.sendRegMail(email, link);
    }
    
    /**
     * sets registration with given id invalid
     * @param id
     */
    public void setRegistrationInvalid(Long id) {
        if(id == null) return;
        Registration reg = dao.find(Registration.class, id);
        this.setRegistrationInvalid(reg);
    }
    
    /**
     * sets registration invalid
     * @param reg
     */
    public void setRegistrationInvalid(Registration reg) {
        if(reg == null) return;
        reg.setValidLink(false);
        dao.put(reg);
    }
    
    /**
     * deletes registration with given id
     * @param id
     */
    public void deleteRegistration(Long id) {
        if(id == null) return;
        Registration reg = dao.find(Registration.class, id);
        this.deleteRegistration(reg);
    }

    /**
     * deletes registration
     * @param reg
     */
    public void deleteRegistration(Registration reg) {
        if(reg == null) return;
        dao.delete(reg);
    }
    
    
    /**
     * search for loginId in database
     * 
     * @param loginId
     * @return true if given login is unique
     */
    public boolean isLoginUnique(String login) {
        AccountSecurity accsec = dao.ofy().query(AccountSecurity.class).filter("login", login).get();
        if (accsec == null)
            return true;
        return false;
    }


}
